﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace Minestorm.Evolved.Animation
{
    public interface IEasing
    {
        EasingManager EasingManager { get; set; }

        EasingState State { get; }
        TimeSpan LastRun { get; }

        void Update(GameTime gameTime);

        void Stop();

        void Start();

        void Reset();

        void Reverse();
    }

    public class Vector3Easing : Easing
    {
        private SingleEasing _easingX;
        private SingleEasing _easingY;
        private SingleEasing _easingZ;

        public Vector3Easing(Vector3 initial, Vector3 final, float duration, EasingType func)
        {
            Reset(initial, final, duration, func);
        }

        public Vector3Easing()
            : this(Vector3.Zero, Vector3.Zero, 0, null)
        { }

        public Vector3 Value
        {
            get { return new Vector3(_easingX.Value, _easingY.Value, _easingZ.Value); }
        }

        public override bool AutoReset
        {
            get
            {
                return _easingX.AutoReset;
            }
            set
            {
                _easingX.AutoReset = _easingY.AutoReset = _easingZ.AutoReset = value;
            }
        }

        public override bool AutoRestart
        {
            get
            {
                return _easingX.AutoRestart;
            }
            set
            {
                _easingX.AutoRestart = _easingY.AutoRestart = _easingZ.AutoRestart = value;
            }
        }

        public override bool ReverseOnFinish
        {
            get
            {
                return _easingX.ReverseOnFinish;
            }
            set
            {
                _easingX.ReverseOnFinish = _easingY.ReverseOnFinish = _easingZ.ReverseOnFinish = value;
            }
        }

        public override void Update(GameTime gameTime)
        {
            _easingX.Update(gameTime);
            _easingY.Update(gameTime);
            _easingZ.Update(gameTime);
        }

        public override void Reset()
        {
            _easingX.Reset();
            _easingY.Reset();
            _easingZ.Reset();
        }

        public void Reset(Vector3 initial, Vector3 final, float duration, EasingType func)
        {
            _easingX = new SingleEasing(initial.X, final.X, duration, func);
            _easingY = new SingleEasing(initial.Y, final.Y, duration, func);
            _easingZ = new SingleEasing(initial.Z, final.Z, duration, func);
        }

        public override void Reverse()
        {
            _easingX.Reverse();
            _easingY.Reverse();
            _easingZ.Reverse();
        }
    }

    public abstract class Easing : IEasing
    {
        protected const float FPS = 60;
        public const float Interval = 1000f / FPS;

        public delegate float EasingType(float t, float c, float b, float d);

        public EasingState State { get; private set; }
        public EasingType Func { get; protected set; }
        public EasingManager EasingManager { get; set; }
        public virtual bool ReverseOnFinish { get; set; }
        public virtual bool AutoRestart { get; set; }
        public virtual bool AutoReset { get; set; }
        public TimeSpan LastRun { get; private set; }

        public virtual void Update(GameTime gameTime)
        {
            LastRun = gameTime.TotalGameTime;
        }

        public virtual void Stop()
        {
            State = EasingState.Stopped;
        }

        public virtual void Start()
        {
            State = EasingState.Started;
        }

        public abstract void Reset();

        public abstract void Reverse();
    }

    public class SingleEasing : Easing
    {
        public SingleEasing()
            : this(0, 0, 0, null)
        { }

        public SingleEasing(float initial, float final, float duration, EasingType func)
        {
            Reset(initial, final, duration, func);
        }

        public event EventHandler<EasingEventArgs> Finished;

        public float ChangeInValue { get; private set; }

        public float Initial { get; set; }
        public float Final { get; set; }
        public float Duration { get; set; }
        public float Ticks { get; private set; }
        public float CurrentTick { get; private set; }
        public float Change { get; private set; }
        public float Value { get; private set; }

        public override void Update(GameTime gameTime)
        {
            CurrentTick++;

            float lastValue = Value;

            if (CurrentTick > Ticks)
            {
                Stop();

                if (ReverseOnFinish)
                    Reverse();

                if (AutoReset || AutoRestart)
                    Reset();

                if (AutoRestart)
                    Start();
                else if (Finished != null)
                    Finished(EasingManager, new EasingEventArgs(this));
            }
            else
            {
                Value = Func(CurrentTick, Change, Initial, Ticks);
            }

            ChangeInValue = Value - lastValue;

            base.Update(gameTime);
        }

        public override void Reset()
        {
            Value = Initial;
            CurrentTick = 0;
        }

        public void Reset(float initial, float final, float duration, EasingType func)
        {
            Value = initial;
            Initial = initial;
            Final = final;
            Duration = duration;
            Func = func ?? EasingTypes.None;
            Ticks = duration * Easing.FPS;
            Change = Final - Initial;
            CurrentTick = 0;
            ReverseOnFinish = false;
            AutoRestart = false;
        }

        public override void Reverse()
        {
            float initial = Final;
            Final = Initial;
            Initial = initial;
            Change = Final - Initial;

            Reset();
        }
    }
}
